#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
#include<ctype.h>
#define SZ 100
#define NUM 10
#define RED "\033[31m"
#define RESET "\033[0m"
#define None_Redir 0
#define In_Redir 1
#define Out_Redir 2
#define App_Redir 3
#define Skip(p) do{p+=strlen(p)-1; while(*p != '/')p--;}while(0)
#define SkipSpace(command,pos)do{while(*(command+pos) == ' ')pos++;}while(0)
//#define SkipSpace(command,pos)do{while(1){if(isspace(command[pos]))pos++;else break;}}while(0)
//定义个全局字符指针数组,每次存入一个命令及其参数
char* gArgv[NUM]= {NULL};
char cwd[SZ];
int lastcode = 0;
int redir_type = None_Redir;
char* filename = NULL;

char* GetUserName()
{ 
  char* name = getenv("USER"); 
  return name == NULL ? "unknown" : name;
}
char* GetHostName()
{
  char* name = getenv("HOSTNAME"); 
  return name == NULL ? "unknown" : name;
}
char* GetCwdName()
{
  char* name = getenv("PWD");
  return name == NULL ? "unknown" : name;
}
void PrintPrompt(char prompt[],size_t size)
{

  char* username = GetUserName();
  char* hostname = GetHostName();
  char* cwd = GetCwdName();
  //cwd默认显示该路径的最后一个文件
  Skip(cwd);
  //往缓冲区写入
  snprintf(prompt,size,"[%s%s%s:%s%s%s:%s%s%s]:",RED,username,RESET,RED,hostname,RESET,RED,strlen(cwd)==1 ?"/" : cwd+1,RESET);
  printf("%s",prompt);
  //fflush(stdout);
}
void PrintCommand(char command[],size_t size)
{
  //fgets保证字符串以\0结束，且会读取\n
  char*str =  fgets(command,size,stdin);
  command[strlen(command)-1] = '\0';
}
void CheckRedir(char command[])
{
  //定义文件开始和结束
  int pos = 0;
  int end = strlen(command) - 1;
  while(pos != end)
  {
    if(command[pos] == '>')
    {
      if(command[pos+1] == '>')
      {
          command[pos++] = '\0';
          pos++;//跳过第二个>
          redir_type = App_Redir;
          SkipSpace(command,pos);
          filename = command+pos;

      }
      else{
          command[pos++] = '\0';
          redir_type = Out_Redir;
          SkipSpace(command,pos);
          filename = command+pos;
      }
    }
    else if(command[pos] == '<')
    {
          command[pos++] = '\0';
          redir_type = In_Redir;
          SkipSpace(command,pos);
          filename = command+pos;
    }
    else{
        pos++;
    }
  }

}
void SegCommand(char command[])
{
  const char* symbol = " ";
  gArgv[0] = strtok(command,symbol);
  int index = 1;
  while((gArgv[index++] = strtok(NULL,symbol))){};
}
void ExecuteWork()
{
  pid_t id = fork();
  if(id == 0)
  {
    //判断是否重定向
    if(filename)
    {
      if(redir_type == In_Redir)
      {
          int fd = open(filename,O_RDONLY);
          dup2(fd,0);
      }
      else if(redir_type == Out_Redir)
      {

          int fd = open(filename,O_WRONLY| O_CREAT | O_TRUNC,0666);
          dup2(fd,1);
      }
      else
      {
          int fd = open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);
          dup2(fd,1);

      }
    }
    //子进程
    execvp(gArgv[0],gArgv);
    //终止当前进程，并返回errno给父进程，通过wait或waitpid
    //errno存储最近一次的系统调用或库函数失败的错误码
    perror("execvp failed");
    exit(errno);
    
  }
  //父进程等待子进程
  int status = 0;
  pid_t rid = waitpid(id,&status,0);//以阻塞状态等待
  if(rid > 0)
  {
    //父进程获取子进程的退出码
    lastcode = WEXITSTATUS(status);
    if(lastcode)
    {
      printf("children end with code:%d\n",lastcode);
    }
  }
  
}
void Cd()
{
  const char* path = gArgv[1] == NULL ? "/" : gArgv[1];
  //改变过工作路径
  chdir(path);

  //刷新环境变量
  char tmp[SZ];
  //获取当前进程的绝对工作目录
  getcwd(tmp,sizeof(tmp));
  //给cwd数组写入”PWD=....“
  snprintf(cwd,sizeof(cwd),"PWD=%s",tmp);
  putenv(cwd);
}
//内建命令由父进程执行
int CheckBuidIn()
{
  int yes = 0;
  if(strcmp(gArgv[0],"cd") == 0)
  {
    yes = 1;
    Cd();
  }
  else if(strcmp(gArgv[0],"echo") == 0 && strcmp(gArgv[1],"$?") == 0)
  {
    yes = 1;
    printf("end code:%d\n",lastcode);
  }
  else return yes;
}
int main()
{
  while(1)
  {
    filename = NULL;
    redir_type = None_Redir;
  //1.打印命令行提示符[用户名：型号：路径]：
  char prompt[SZ];
  size_t size = sizeof(prompt);
  PrintPrompt(prompt,size);
  //2.打印命令行参数
  char command[SZ];
  size_t c_size = sizeof(command);
  PrintCommand(command,c_size);
  //2.1判断是否重定向
  CheckRedir(command);
  //3.分割字符串
  SegCommand(command);
  //4.检查是否为内建命令
  int n =  CheckBuidIn();
  if(n) continue;
  //5.创建子进程，执行指令
  ExecuteWork();
  
  }
  return 0;
}

